using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;

namespace WeakEvent
{
	internal static class FastSmartWeakEventForwarderProvider
	{
		internal delegate bool ForwarderDelegate(WeakReference wr, object sender, EventArgs e);

		private static readonly MethodInfo getTarget = typeof(WeakReference).GetMethod("get_Target");

		private static readonly Type[] forwarderParameters = new Type[3]
		{
			typeof(WeakReference),
			typeof(object),
			typeof(EventArgs)
		};

		private static readonly Dictionary<MethodInfo, ForwarderDelegate> forwarders = new Dictionary<MethodInfo, ForwarderDelegate>();

		internal static ForwarderDelegate GetForwarder(MethodInfo method)
		{
			lock (forwarders)
			{
				ForwarderDelegate value;
				if (forwarders.TryGetValue(method, out value))
				{
					return value;
				}
			}
			if (method.DeclaringType.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length != 0)
			{
				throw new ArgumentException("Cannot create weak event to anonymous method with closure.");
			}
			ParameterInfo[] parameters = method.GetParameters();
			DynamicMethod dynamicMethod = new DynamicMethod("FastSmartWeakEvent", typeof(bool), forwarderParameters, method.DeclaringType);
			ILGenerator iLGenerator = dynamicMethod.GetILGenerator();
			if (!method.IsStatic)
			{
				iLGenerator.Emit(OpCodes.Ldarg_0);
				iLGenerator.EmitCall(OpCodes.Callvirt, getTarget, null);
				iLGenerator.Emit(OpCodes.Dup);
				Label label = iLGenerator.DefineLabel();
				iLGenerator.Emit(OpCodes.Brtrue, label);
				iLGenerator.Emit(OpCodes.Pop);
				iLGenerator.Emit(OpCodes.Ldc_I4_1);
				iLGenerator.Emit(OpCodes.Ret);
				iLGenerator.MarkLabel(label);
			}
			iLGenerator.Emit(OpCodes.Ldarg_1);
			iLGenerator.Emit(OpCodes.Ldarg_2);
			iLGenerator.Emit(OpCodes.Castclass, parameters[1].ParameterType);
			iLGenerator.EmitCall(OpCodes.Call, method, null);
			iLGenerator.Emit(OpCodes.Ldc_I4_0);
			iLGenerator.Emit(OpCodes.Ret);
			ForwarderDelegate forwarderDelegate = (ForwarderDelegate)dynamicMethod.CreateDelegate(typeof(ForwarderDelegate));
			lock (forwarders)
			{
				forwarders[method] = forwarderDelegate;
				return forwarderDelegate;
			}
		}
	}
}
